package core.manager;

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.log4j.Logger;

import core.descriptors.TaskDescriptor;
import core.factory.DiscoveryProxyFactory;
import core.interfaces.AbstractDispatcher;
import core.interfaces.Discovery;
import core.interfaces.WFQueue;
import core.util.COREServiceRegistry;
import core.util.IDTaskDescriptorManager;
import core.worker.Address;

/**
 * La classe Manager è il cuore del sistema. Essa si occupa di schedulare la sequenza dei workflow e trasformare i workflow in activity pronti per essere schedulati nei vari taskQueue
 * Contiene una lista (ConcurrentLinkedQueue) di Activity, WorkerNetwork, DiscoveryProxyFactory, Discovery, WFQueue
 * La sequenza di ingresso dei task è la seguente:
 * <ol>
 * <li> Il workflow viene accodato tramite l'enqueue nella WorklflowQueue</li>
 * <li> Il ManagerQueueMonitor rileva la presenza di elementi nella WorkflowQueue e lancia il metodo schedule in maniera asincrona</li>
 * <li> Il processo di schedulazione crea l'activity e aggiunge l'activity nella lista delle activities</li>
 * </ol>
 *
 * @author Vincenzo Merlo, Scuderi Giovanni Luca, Paolo Giuffrida
 */
public class Manager implements AbstractDispatcher {

    private final Logger logger = COREServiceRegistry.getLogger(Manager.class.toString()); // istanza log4j
    private ConcurrentLinkedQueue<Activity> activities; // struttura dati contenente le activity    
    private DiscoveryProxyFactory discoveryProxyFactory; // factory del proxy del discovery
    private Discovery workerDiscoveryProxy; // proxy del discovery
    private WFQueue wfqueue; // coda dei workflow
    private ManagerQueueMonitor monitor; // thread per l'estrazione degli elementi dalla coda di schedulazione
    // volatile is needed so that multiple threads can reconcile the instance
    // semantics for volatile changed in Java 5.
    private volatile static Manager instance;
    //private boolean running = false;

    /**
     * metodo per singleton
     * @return ritorna istanza singleton
     */
    public static Manager getInstance() {
        // needed because once there is singleton available no need to acquire
        // monitor again & again as it is costly
        if (instance == null) {
            synchronized (Manager.class) {
                // this is needed if two threads are waiting at the monitor at the
                // time when singleton was getting instantiated
                if (instance == null) {
                    instance = new Manager();
                    instance.run();
                }
            }
        }
        return (instance);
    }

    /*
     * metodo privato per singleton
     * istanzia la factory, i proxy e il queuemanager
     */
    private Manager() {
        logger.trace("costructor : factory discovery");
        this.discoveryProxyFactory = DiscoveryProxyFactory.getInstance();
        logger.trace("costructor : factory -> getProxy ");
        this.workerDiscoveryProxy = discoveryProxyFactory.getProxy();
        logger.trace("costructor : activity");
        activities = new ConcurrentLinkedQueue<Activity>();
        logger.trace("costructor : WfQueueManager");
        wfqueue = new WFQueueManager();

    }

    /**
     * istanzia il thread di estrazione dalla coda dei Workflow
     */
    public void run() {
        logger.trace("run!!!!!");
        ExecutorService threadExecutor = Executors.newSingleThreadExecutor();
        monitor = new ManagerQueueMonitor(this);
        threadExecutor.execute(monitor);
    }

    /**
     * Schedulazione dei workflow: per ogni singolo task assegna un worker, crea l'activity e la aggiunge localmente
     */
    public void schedule() {
        logger.trace("schedule");
        Workflow wf = dequeue();
        TaskDescriptor td;
        Address addr;
        WorkerNetwork wn = new WorkerNetwork();
        for (int i = 0; i < wf.getTasks().size(); i++) {
            td = wf.getTask(i);
            td.setID(IDTaskDescriptorManager.getInstance().getID());
            addr = this.workerDiscoveryProxy.getWorker(td);
            wn.addWorker(addr); //TODO: check int returned
        }
        Activity act = new Activity();
        logger.trace("Workflow dequeued SIZE" + wf.getTasks().size()); //NON CAMBIARE
        for (int i = 0; i < wf.getTasks().size(); i++) {
            logger.trace("Workflow Task :" + i + " instance" + wf.getTasks().toArray()[i]); //NON CAMBIARE
        }

        act.setWf(wf);

        act.setSbn(wn);
        this.activities.add(act);

        act.scheduleActivity();
    }

    /**
     * Ritorna la lista delle Activities presenti
     * @return lista, ConcurrentLinkedQueue, delle Activity presenti
     */
    public ConcurrentLinkedQueue<Activity> getActivities() {
        logger.trace("getActivities");
        return activities;
    }

    /**
     * Setta la lista delle Activities
     * @param lista, ConcurrentLinkedQueue, delle Activity da settare
     */
    public void setActivities(ConcurrentLinkedQueue<Activity> activities) {
        logger.trace("setActivies");
        this.activities = activities;
    }

    /**
     * Ritorna la coda dei Workflow
     * @return WFQueue, coda dei Workflow
     */
    public WFQueue getWfQueue() {
        logger.trace("getWfqueue");
        return wfqueue;
    }

    /**
     * Setta coda di schedulazione dei workflows
     * @param coda, WFQueue, di Workflows da settare
     */
    public void setWfQueue(WFQueueManager wfqueue) {
        logger.trace("setWfqueue");
        this.wfqueue = wfqueue;
    }

    /**
     * metodo che accoda i workflow nel queuemanager locale in modo tale da essere schedulati
     * @param wf Workflow completo da schedulare
     * @return posizione interna del workflow
     */
    public int enqueue(Workflow wf) {
        int i;
        final WFQueueManager queue = (WFQueueManager) wfqueue;
        synchronized (queue) {
            logger.info("enqueue");

            //synchronized (wfqueue) {
            i = ((WFQueueManager) this.wfqueue).enqueue(wf);
            logger.info("enqueue & notify");
            queue.notify();
            //}
        }
        return i;
    }

    /*
     * funzione di estrazione del workflow, usato nella schedulazione chiamata dal ManagerQueueMonitor
     * @return Workflow pronto all'estrazione
     *
     */
    private synchronized Workflow dequeue() {
        Workflow i = ((WFQueueManager) this.wfqueue).dequeue();
        return i;
    }

    /**
     * Metodo toString
     * @return Stringa formattata rappresentante la classe manager
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(Manager.class.getSimpleName());
        sb.append("\n\tWFQueue: ").append(this.wfqueue).append("\n\tDiscovery: ").append(this.workerDiscoveryProxy).append("\n\tDiscoveryProxyFactory: ").append(this.discoveryProxyFactory);

        for (Activity act : this.getActivities()) {
            sb.append("\n\t").append(act);
        }

        return sb.toString();
    }
}
